/*
 * Copyright (c) 2024 Arm Limited. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the License); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an AS IS BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * -----------------------------------------------------------------------------
 *
 * $Date:       7. November 2024
 * $Revision:   V3.1
 *
 * Project:     USB Device Driver for STMicroelectronics STM32 devices
 *
 * -----------------------------------------------------------------------------
 */

/*! \page usbd_stm32 USB Device

# Revision History

- Version 3.1
  - Corrected PMA configuration
  - Added support for devices without Vbus sensing capability
  - Updated GetFrameNumber function to always return 0 because of missing HAL support
- Version 3.0
  - Initial release

# Requirements

This driver requires the STM32 device specific **HAL** and **STM32CubeMX** (CubeMX) initialization code generator.
The driver instance is mapped to hardware as shown in the table below:

  CMSIS Driver Instance | STM32 Hardware Resource
  :---------------------|:-----------------------
  Driver_USBD0          | USB_OTG_FS or USB_FS or USB_DRD_FS
  Driver_USBD1          | USB_OTG_HS

# Deviations

This driver has the following deviations from the CMSIS-Driver specification:

__Conceptual__ deviations:
  - CubeMX generated initialization code (function MX_USB_...PCD_Init) already configures
    the peripheral. Power, clocks, pins, and interrupts are enabled after execution
    of initialization that executes in `main.c`.
  - Role (Device to Host) cannot be changed at run-time.

__Functional__ deviations:
  - GetCapabilities:
    - depends on the code generated by CubeMX.
  - Initialize:
    - depends on the code generated by CubeMX.
    - does not initialize the pins.
  - Uninitialize:
    - does not de-initialize the pins.
  - PowerControl:
    - depends on the code generated by CubeMX.
    - low-power mode is not supported by HAL.
  - EndpointTransfer:
    - on Endpoint 0 the HAL does not support transfers longer than maximum packet size.
  - EndpointTransferAbort:
    - HAL function fails when aborting transfer on Interrupt OUT Endpoint.

# CubeMX Configuration

This driver requires the following configuration in CubeMX:

  - **clock**: **USB** peripheral clock at **48 MHz**.
  - **peripheral**:
    - for **USB** or **USB_OTG_FS** configured as **Device (FS)** or **Mode = Device_Only**
      , and configure the following **Parameter Settings**:
      - **Enable internal IP DMA** set to **Disabled**.
      - **Battery charging**, if it exists, set to **Disabled**.
    - for **USB_OTG_HS** in **high-speed** mode: configured as **External Phy = Device_Only** if ULPI Phy is populated
      , and configure the following **Parameter Settings**:
      - **Enable internal IP DMA** set to **Disabled**.
      - **Speed** set to speed required by the application:
        if the application is configured for full-speed operation set this setting to **Device Full Speed 12MBit/s**,
        if the application is configured for high-speed operation set this setting to **Device High Speed 480MBit/s**.
      - **Battery charging**, if it exists, set to **Disabled**.
    - for **USB_OTG_HS** in **full-speed** mode: configured as **Internal FS Phy = Device_Only** if ULPI Phy is not populated
      , and configure the following **Parameter Settings**:
      - **Enable internal IP DMA** set to **Disabled**.
      - **Battery charging**, if it exists, set to **Disabled**.
  - **pins**:
    - for **USB** or **USB_OTG_FS** in **full-speed** mode: **USB_OTG_FS_VBUS**, **USB_OTG_FS_DM** and **USB_OTG_FS_DP pins**.
    - for **USB_OTG_HS** in **high-speed** mode: **USB_OTG_HS_ULPI_CK**, **USB_OTG_HS_ULPI_D0**, **USB_OTG_HS_ULPI_D1**
      , **USB_OTG_HS_ULPI_D2**, **USB_OTG_HS_ULPI_D3**, **USB_OTG_HS_ULPI_D4**, **USB_OTG_HS_ULPI_D5**, **USB_OTG_HS_ULPI_D6**
      , **USB_OTG_HS_ULPI_D7**, **USB_OTG_HS_ULPI_DIR**, **USB_OTG_HS_ULPI_NXT** and **USB_OTG_HS_ULPI_STP pins**.
    - for **USB_OTG_HS** in **full-speed** mode: **USB_OTG_HS_VBUS**, **USB_OTG_HS_DM** and **USB_OTG_HS_DP pins**.
  - **interrupts**:
    - for **USB** or **USB_FS** or **USB_OTG_FS** enabled **USB** or **USB FS global interrupt** or **USB On The Go FS global interrupt**
      and **IRQ handlers** that **Call HAL handlers**.
    - for **USB_OTG_HS** enabled **USB On The Go HS global interrupt** and **IRQ handlers** that **Call HAL handlers**.

## Example

### Pinout & Configuration tab

  1. In the **Pinout view** window click on a pin and select it's functionality:
       Pin      | Functionality
       :--------|:--------------------:
       PA9      | **USB_OTG_FS_VBUS** (required for VBUS sensing)
       PA11     | **USB_OTG_FS_DM**
       PA12     | **USB_OTG_FS_DP**
       PA5      | **USB_OTG_HS_ULPI_CK**
       PA3      | **USB_OTG_HS_ULPI_D0**
       PB0      | **USB_OTG_HS_ULPI_D1**
       PB1      | **USB_OTG_HS_ULPI_D2**
       PB10     | **USB_OTG_HS_ULPI_D3**
       PB11     | **USB_OTG_HS_ULPI_D4**
       PB12     | **USB_OTG_HS_ULPI_D5**
       PB13     | **USB_OTG_HS_ULPI_D6**
       PB5      | **USB_OTG_HS_ULPI_D7**
       PI11     | **USB_OTG_HS_ULPI_DIR**
       PH4      | **USB_OTG_HS_ULPI_NXT**
       PC0      | **USB_OTG_HS_ULPI_STP**
     \n

  2. Under **Categories**: **Connectivity** select **USB_OTG_FS**:

     __Mode__:
       - Mode: **Device_Only**
       - Activate_VBUS: **VBUS sensing**
       - Activate_SOF: unchecked

     __Configuration__:
       - Parameter Settings:
         - Enable internal IP DMA: **Disabled**
         - Battery charging: **Disabled**
       - GPIO Settings:
           Pin Name | Signal on Pin       | Pin Context..| GPIO output..| GPIO mode                     | GPIO Pull-up/Pull..| Maximum out..| Fast Mode | User Label
           :--------|:-------------------:|:------------:|:------------:|:-----------------------------:|:------------------:|:------------:|:---------:|:----------:
           PA9      | USB_OTG_FS_VBUS     | n/a          | n/a          | Input mode                    | No pull-up and no..| n/a          | n/a       | .
           PA11     | USB_OTG_FS_DM       | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PA12     | USB_OTG_FS_DP       | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
         \n

  3. Under **Categories**: **Connectivity** select **USB_OTG_HS**:

     __Mode__:
       - External Phy: **Device_Only**
       - Internal FS Phy: Disable
       - Activate_SOF: unchecked
       - Activate_VBUS: Disable

     __Configuration__:
       - Parameter Settings:
         - Speed: set to **speed required by the application**
         - Enable internal IP DMA: **Disabled**
         - Battery charging: **Disabled**
       - GPIO Settings:
           Pin Name | Signal on Pin       | Pin Context..| GPIO output..| GPIO mode                     | GPIO Pull-up/Pull..| Maximum out..| Fast Mode | User Label
           :--------|:-------------------:|:------------:|:------------:|:-----------------------------:|:------------------:|:------------:|:---------:|:----------:
           PA5      | USB_OTG_HS_ULPI_CK  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PA3      | USB_OTG_HS_ULPI_D0  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PB0      | USB_OTG_HS_ULPI_D1  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PB1      | USB_OTG_HS_ULPI_D2  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PB10     | USB_OTG_HS_ULPI_D3  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PB11     | USB_OTG_HS_ULPI_D4  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PB12     | USB_OTG_HS_ULPI_D5  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PB13     | USB_OTG_HS_ULPI_D6  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PB5      | USB_OTG_HS_ULPI_D7  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PI11     | USB_OTG_HS_ULPI_DIR | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PH4      | USB_OTG_HS_ULPI_NXT | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PC0      | USB_OTG_HS_ULPI_STP | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
         \n

  4. Under **Categories**: **System Core** select **NVIC**:

     __Configuration__:
       - NVIC:
           NVIC Interrupt Table              | Enabled     | Preemption Priority | Sub Priority
           :---------------------------------|:-----------:|:-------------------:|:------------:
           USB On The Go HS global interrupt | **checked** | 0                   | 0
           USB On The Go FS global interrupt | **checked** | 0                   | 0
         \n

       - Code generation:
           Enabled interrupt table           | Select for..| Generate Enable in..| Generate IRQ h.. | Call HAL handler
           :---------------------------------|:-----------:|:-------------------:|:----------------:|:----------------:
           USB On The Go HS global interrupt | unchecked   | checked             | checked          | checked
           USB On The Go FS global interrupt | unchecked   | checked             | checked          | checked
         \n

### Clock Configuration tab

  1. Configure **To USB (MHz)**: **48**

### Project Manager tab

  1. Under **Advanced Settings**:

     __Generated Function Calls__:
       Generate Code | Function Name               | Peripheral Inst..| Do not generate ..| Visibility (Static)
       :-------------|:---------------------------:|:----------------:|:-----------------:|:-------------------:
       checked       | MX_USB_OTG_FS_PCD_Init      | USB_OTG_FS       | unchecked         | checked
       checked       | MX_USB_OTG_HS_PCD_Init      | USB_OTG_HS       | unchecked         | checked
*/

/*! \cond */

#include "USBD_STM32.h"

#include "RTE_Components.h"
#include  CMSIS_device_header

#include <string.h>

// Driver Version **************************************************************
                                                //  CMSIS Driver API version           , Driver version
static  const ARM_DRIVER_VERSION driver_version = { ARM_DRIVER_VERSION_MAJOR_MINOR(2,3), ARM_DRIVER_VERSION_MAJOR_MINOR(3,1) };
// *****************************************************************************

// Compile-time configuration **************************************************

// Configuration depending on MX_Device.h

// Check if at least one peripheral instance is configured in STM32CubeMX
#if    (!defined(MX_USBD0) && \
        !defined(MX_USBD1))
#error  USB Device driver requires at least one USB (Device) peripheral configured in STM32CubeMX!

// Check if MX_Device.h version is as required (old version did not have all the necessary information)
#elif  (!defined(MX_DEVICE_VERSION) || (MX_DEVICE_VERSION < 0x01000000U))
#error  USB Device driver requires new MX_Device.h configuration, please regenerate MX_Device.h file!

#else
#define DRIVER_CONFIG_VALID             1
#endif

// Determine peripheral differences that driver needs to handle

// Determine if peripheral uses PMA
// If USBD_VARIANT_PMA == 1 then function HAL_PCDEx_PMAConfig is used for Endpoint FIFO
// if USBD_VARIANT_PMA == 0 then functions HAL_PCDEx_SetTxFiFo and HAL_PCDEx_SetRxFiFo are used
// HAL_PCDEx_PMAConfig is used on older devices that have older USB controller (not OTG)
#ifdef  MX_USB
#define USBD_VARIANT_PMA                1
#else
#define USBD_VARIANT_PMA                0
#endif

// Determine if HAL supports Vbus sensing
#if   ((defined(USB_DRD_FS) || defined(USB_OTG_FS) || defined(USB_OTG_HS)) && !defined(STM32U0xx_HAL_H))
#define USBD_VARIANT_VBUS_SENSING       1
#endif

// Configuration depending on the local macros

// Compile-time configuration (that can be externally overridden if necessary)
// Maximum number of endpoints
#ifndef USBD_MAX_ENDPOINT_NUM
#define USBD_MAX_ENDPOINT_NUM           (16U)
#endif

// Maximum packet size for Endpoint 0
#ifndef USBD_EP0_MAX_PACKET_SIZE
#define USBD_EP0_MAX_PACKET_SIZE        (64U)
#endif

// *****************************************************************************

#ifdef  DRIVER_CONFIG_VALID     // Driver code is available only if configuration is valid

// Macros
// Macro for section for RW info
#ifdef  USBD_SECTION_NAME
#define USBDn_SECTION_(name,n)  __attribute__((section(name #n)))
#define USBDn_SECTION(n)        USBDn_SECTION_(USBD_SECTION_NAME,n)
#else
#define USBDn_SECTION(n)
#endif

// Macro to create usbd_ro_info and usbd_rw_info (for instances)
#define INFO_DEFINE(n)                                                                                                 \
extern  PCD_HandleTypeDef       MX_USBD##n##_HANDLE;                                                                   \
static        RW_Info_t         usbd##n##_rw_info USBDn_SECTION(n);                                                    \
static  const RO_Info_t         usbd##n##_ro_info = { &MX_USBD##n##_HANDLE,                                            \
                                                      &usbd##n##_rw_info                                               \
                                                    };

// Macro for declaring functions (for instances)
#define FUNCS_DECLARE(n)                                                                                               \
static  ARM_USBD_CAPABILITIES   USBD##n##_GetCapabilities           (void);                                            \
static  int32_t                 USBD##n##_Initialize                (ARM_USBD_SignalDeviceEvent_t   cb_device_event,   \
                                                                     ARM_USBD_SignalEndpointEvent_t cb_endpoint_event);\
static  int32_t                 USBD##n##_Uninitialize              (void);                                            \
static  int32_t                 USBD##n##_PowerControl              (ARM_POWER_STATE state);                           \
static  int32_t                 USBD##n##_DeviceConnect             (void);                                            \
static  int32_t                 USBD##n##_DeviceDisconnect          (void);                                            \
static  ARM_USBD_STATE          USBD##n##_DeviceGetState            (void);                                            \
static  int32_t                 USBD##n##_DeviceRemoteWakeup        (void);                                            \
static  int32_t                 USBD##n##_DeviceSetAddress          (uint8_t  dev_addr);                               \
static  int32_t                 USBD##n##_ReadSetupPacket           (uint8_t *setup);                                  \
static  int32_t                 USBD##n##_EndpointConfigure         (uint8_t  ep_addr,                                 \
                                                                     uint8_t  ep_type,                                 \
                                                                     uint16_t ep_max_packet_size);                     \
static  int32_t                 USBD##n##_EndpointUnconfigure       (uint8_t  ep_addr);                                \
static  int32_t                 USBD##n##_EndpointStall             (uint8_t  ep_addr, bool stall);                    \
static  int32_t                 USBD##n##_EndpointTransfer          (uint8_t  ep_addr,                                 \
                                                                     uint8_t *data,                                    \
                                                                     uint32_t num);                                    \
static  uint32_t                USBD##n##_EndpointTransferGetResult (uint8_t  ep_addr);                                \
static  int32_t                 USBD##n##_EndpointTransferAbort     (uint8_t  ep_addr);                                \
static  uint16_t                USBD##n##_GetFrameNumber            (void);

// Macro for defining functions (for instances)
#define FUNCS_DEFINE(n)                                                                                                                                                                                           \
static  ARM_USBD_CAPABILITIES   USBD##n##_GetCapabilities           (void)                                             { return USBDn_GetCapabilities           (&usbd##n##_ro_info); }                           \
static  int32_t                 USBD##n##_Initialize                (ARM_USBD_SignalDeviceEvent_t   cb_device_event,                                                                                              \
                                                                     ARM_USBD_SignalEndpointEvent_t cb_endpoint_event) { return USBDn_Initialize                (&usbd##n##_ro_info, cb_device_event, cb_endpoint_event); }   \
static  int32_t                 USBD##n##_Uninitialize              (void)                                             { return USBDn_Uninitialize              (&usbd##n##_ro_info); }                           \
static  int32_t                 USBD##n##_PowerControl              (ARM_POWER_STATE state)                            { return USBDn_PowerControl              (&usbd##n##_ro_info, state); }                    \
static  int32_t                 USBD##n##_DeviceConnect             (void)                                             { return USBDn_DeviceConnect             (&usbd##n##_ro_info); }                           \
static  int32_t                 USBD##n##_DeviceDisconnect          (void)                                             { return USBDn_DeviceDisconnect          (&usbd##n##_ro_info); }                           \
static  ARM_USBD_STATE          USBD##n##_DeviceGetState            (void)                                             { return USBDn_DeviceGetState            (&usbd##n##_ro_info); }                           \
static  int32_t                 USBD##n##_DeviceRemoteWakeup        (void)                                             { return USBDn_DeviceRemoteWakeup        (&usbd##n##_ro_info); }                           \
static  int32_t                 USBD##n##_DeviceSetAddress          (uint8_t  dev_addr)                                { return USBDn_DeviceSetAddress          (&usbd##n##_ro_info, dev_addr); }                 \
static  int32_t                 USBD##n##_ReadSetupPacket           (uint8_t *setup)                                   { return USBDn_ReadSetupPacket           (&usbd##n##_ro_info, setup); }                    \
static  int32_t                 USBD##n##_EndpointConfigure         (uint8_t  ep_addr,                                                                                                                            \
                                                                     uint8_t  ep_type,                                                                                                                            \
                                                                     uint16_t ep_max_packet_size)                      { return USBDn_EndpointConfigure         (&usbd##n##_ro_info, ep_addr, ep_type, ep_max_packet_size); } \
static  int32_t                 USBD##n##_EndpointUnconfigure       (uint8_t  ep_addr)                                 { return USBDn_EndpointUnconfigure       (&usbd##n##_ro_info, ep_addr); }                  \
static  int32_t                 USBD##n##_EndpointStall             (uint8_t  ep_addr, bool stall)                     { return USBDn_EndpointStall             (&usbd##n##_ro_info, ep_addr, stall); }           \
static  int32_t                 USBD##n##_EndpointTransfer          (uint8_t  ep_addr,                                                                                                                            \
                                                                     uint8_t *data,                                                                                                                               \
                                                                     uint32_t num)                                     { return USBDn_EndpointTransfer          (&usbd##n##_ro_info, ep_addr, data, num); }       \
static  uint32_t                USBD##n##_EndpointTransferGetResult (uint8_t  ep_addr)                                 { return USBDn_EndpointTransferGetResult (&usbd##n##_ro_info, ep_addr); }                  \
static  int32_t                 USBD##n##_EndpointTransferAbort     (uint8_t  ep_addr)                                 { return USBDn_EndpointTransferAbort     (&usbd##n##_ro_info, ep_addr); }                  \
static  uint16_t                USBD##n##_GetFrameNumber            (void)                                             { return USBDn_GetFrameNumber            (&usbd##n##_ro_info); }

// Macro for defining driver structures (for instances)
#define USBD_DRIVER(n)                  \
ARM_DRIVER_USBD Driver_USBD##n = {      \
  USBD_GetVersion,                      \
  USBD##n##_GetCapabilities,            \
  USBD##n##_Initialize,                 \
  USBD##n##_Uninitialize,               \
  USBD##n##_PowerControl,               \
  USBD##n##_DeviceConnect,              \
  USBD##n##_DeviceDisconnect,           \
  USBD##n##_DeviceGetState,             \
  USBD##n##_DeviceRemoteWakeup,         \
  USBD##n##_DeviceSetAddress,           \
  USBD##n##_ReadSetupPacket,            \
  USBD##n##_EndpointConfigure,          \
  USBD##n##_EndpointUnconfigure,        \
  USBD##n##_EndpointStall,              \
  USBD##n##_EndpointTransfer,           \
  USBD##n##_EndpointTransferGetResult,  \
  USBD##n##_EndpointTransferAbort,      \
  USBD##n##_GetFrameNumber              \
};

// Endpoint related macros
#define EP_DIR(ep_addr)         (((ep_addr) >> 7) & 1U)
#define EP_OUT_INDEX            (0U)
#define EP_IN_INDEX             (1U)
#define EP_NUM(ep_addr)         (ep_addr & ARM_USB_ENDPOINT_NUMBER_MASK)

// Driver status
typedef struct {
  uint8_t                       initialized  : 1;       // Initialized status: 0 - not initialized, 1 - initialized
  uint8_t                       powered      : 1;       // Power status:       0 - not powered,     1 - powered
  uint8_t                       reserved     : 6;       // Reserved (for padding)
} DriverStatus_t;

// USB Device state
typedef struct {
  volatile uint8_t              vbus;                   // USB Device VBUS state
  volatile uint8_t              speed;                  // USB Device speed (ARM_USB_SPEED_xxx) state
  volatile uint8_t              active;                 // USB Device active state
} USBD_State_t;

// Endpoint information
typedef struct {
  volatile uint16_t             configured;             // Endpoint configuration status
           uint16_t             max_packet_size;        // Maximum packet size (in bytes)
  volatile uint32_t             num_transferred_total;  // Number of totally transferred bytes
  volatile uint32_t             num_transferring;       // Number of transferred bytes in last transfer
} EP_Info_t;

// Instance run-time information (RW)
typedef struct {
  ARM_USBD_SignalDeviceEvent_t  cb_device_event;        // Device event callback
  ARM_USBD_SignalEndpointEvent_t cb_endpoint_event;     // Endpoint event callback
  DriverStatus_t                drv_status;             // Driver status
  USBD_State_t                  usbd_state;             // USB Device state
  volatile uint32_t             setup_received;         // Setup Packet received flag (0 - not received or read already, 1 - received and unread yet)
  volatile uint32_t             setup_packet[2];        // Setup Packet data
           uint8_t * volatile   ep0_data[2];            // Pointer to Endpoint 0 data (index: 0 - OUT, 1 - IN)
  volatile uint32_t             ep0_num[2];             // Number of bytes to transfer on Endpoint 0 (index: 0 - OUT, 1 - IN)
  EP_Info_t                     ep_info[USBD_MAX_ENDPOINT_NUM][2];      // Endpoint information
} RW_Info_t;

// Instance compile-time information (RO)
// also contains pointer to run-time information
typedef struct {
  PCD_HandleTypeDef            *ptr_hpcd;               // Pointer to PCD handle
  RW_Info_t                    *ptr_rw_info;            // Pointer to run-time information (RW)
} RO_Info_t;

// Information definitions (for instances)
#ifdef MX_USBD0
INFO_DEFINE(0)
#endif
#ifdef MX_USBD1
INFO_DEFINE(1)
#endif

// List of available USBD instance infos
static const RO_Info_t * const usbd_ro_info_list[] = {
#ifdef MX_USBD0
  &usbd0_ro_info,
#endif
#ifdef MX_USBD1
  &usbd1_ro_info,
#endif
  NULL
};

// Local functions prototypes
static const RO_Info_t         *USBD_GetInfo                    (const PCD_HandleTypeDef * const hpcd);
static int32_t                  USBDn_EndpointConfigureBuffer   (const RO_Info_t * const ptr_ro_info, uint8_t ep_addr, uint8_t ep_type, uint16_t ep_max_packet_size);
static ARM_DRIVER_VERSION       USBD_GetVersion                 (void);
static ARM_USBD_CAPABILITIES    USBDn_GetCapabilities           (const RO_Info_t * const ptr_ro_info);
static int32_t                  USBDn_Initialize                (const RO_Info_t * const ptr_ro_info, ARM_USBD_SignalDeviceEvent_t cb_device_event, ARM_USBD_SignalEndpointEvent_t cb_endpoint_event);
static int32_t                  USBDn_Uninitialize              (const RO_Info_t * const ptr_ro_info);
static int32_t                  USBDn_PowerControl              (const RO_Info_t * const ptr_ro_info, ARM_POWER_STATE state);
static int32_t                  USBDn_DeviceConnect             (const RO_Info_t * const ptr_ro_info);
static int32_t                  USBDn_DeviceDisconnect          (const RO_Info_t * const ptr_ro_info);
static ARM_USBD_STATE           USBDn_DeviceGetState            (const RO_Info_t * const ptr_ro_info);
static int32_t                  USBDn_DeviceRemoteWakeup        (const RO_Info_t * const ptr_ro_info);
static int32_t                  USBDn_DeviceSetAddress          (const RO_Info_t * const ptr_ro_info, uint8_t  dev_addr);
static int32_t                  USBDn_ReadSetupPacket           (const RO_Info_t * const ptr_ro_info, uint8_t *setup);
static int32_t                  USBDn_EndpointConfigure         (const RO_Info_t * const ptr_ro_info, uint8_t  ep_addr, uint8_t  ep_type, uint16_t ep_max_packet_size);
static int32_t                  USBDn_EndpointUnconfigure       (const RO_Info_t * const ptr_ro_info, uint8_t  ep_addr);
static int32_t                  USBDn_EndpointStall             (const RO_Info_t * const ptr_ro_info, uint8_t  ep_addr, bool stall);
static int32_t                  USBDn_EndpointTransfer          (const RO_Info_t * const ptr_ro_info, uint8_t  ep_addr, uint8_t *data, uint32_t num);
static uint32_t                 USBDn_EndpointTransferGetResult (const RO_Info_t * const ptr_ro_info, uint8_t  ep_addr);
static int32_t                  USBDn_EndpointTransferAbort     (const RO_Info_t * const ptr_ro_info, uint8_t  ep_addr);
static uint16_t                 USBDn_GetFrameNumber            (const RO_Info_t * const ptr_ro_info);

// Local driver functions declarations (for instances)
#ifdef MX_USBD0
FUNCS_DECLARE(0)
#endif
#ifdef MX_USBD1
FUNCS_DECLARE(1)
#endif

// Auxiliary functions

/**
  \fn          RO_Info_t *USBD_GetInfo (const PCD_HandleTypeDef * const hpcd)
  \brief       Get pointer to RO_Info_t structure corresponding to specified hpcd.
  \param[in]   hpcd     Pointer to USBD handle structure (PCD_HandleTypeDef)
  \return      pointer to USBD RO info structure (RO_Info_t)
*/
static const RO_Info_t *USBD_GetInfo (const PCD_HandleTypeDef * const hpcd) {
  const RO_Info_t *ptr_ro_info;
        uint8_t    i;

  ptr_ro_info = NULL;
  i            = 0U;

  // Find USBD which uses same hpcd handle as parameter hpcd
  for (i = 0U; i < (sizeof(usbd_ro_info_list)/sizeof(RO_Info_t *)); i++) {
    if (usbd_ro_info_list[i] != NULL) {
      if (usbd_ro_info_list[i]->ptr_hpcd == hpcd) {
        ptr_ro_info = usbd_ro_info_list[i];
        break;
      }
    }
  }

  return ptr_ro_info;
}

/**
  \fn          int32_t USBDn_EndpointConfigureBuffer (const RO_Info_t * const ptr_ro_info, uint8_t ep_addr, uint8_t ep_type, uint16_t ep_max_packet_size)
  \brief       Configure buffer for USB Endpoint (separate IN and OUT).
  \detail      There are 2 different types of USB controllers. One uses HAL_PCDEx_PMAConfig
               for buffer configuration, and the other uses HAL_PCDEx_SetTxFiFo and HAL_PCDEx_SetRxFiFo
               functions.
  \param[in]   ptr_ro_info     Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   ep_addr  Endpoint Address
                - ep_addr.0..3: Address
                - ep_addr.7:    Direction
  \param[in]   ep_type  Endpoint Type (ARM_USB_ENDPOINT_xxx)
  \param[in]   ep_max_packet_size Endpoint Maximum Packet Size
  \return      \ref execution_status
*/
static int32_t USBDn_EndpointConfigureBuffer (const RO_Info_t * const ptr_ro_info, uint8_t ep_addr, uint8_t ep_type, uint16_t ep_max_packet_size) {

#if (USBD_VARIANT_PMA == 0)             // If using HAL_PCDEx_SetTxFiFo and HAL_PCDEx_SetRxFiFo functions
  uint16_t rx_fifo_size;
  uint16_t max_packet_size;
  uint8_t  ep_num;

  (void)ep_type;

  if (EP_DIR(ep_addr) != 0U) {          // IN Endpoint
    // IN endpoint
    if (HAL_PCDEx_SetTxFiFo(ptr_ro_info->ptr_hpcd, EP_NUM(ep_addr), (ep_max_packet_size + 3U) / 4U) != HAL_OK) {
      return ARM_DRIVER_ERROR_PARAMETER;
    }
  } else {                              // OUT Endpoint
    max_packet_size = ptr_ro_info->ptr_rw_info->ep_info[0U][EP_OUT_INDEX].max_packet_size;

    // Find largest max packet size
    for (ep_num = 1U; ep_num < USBD_MAX_ENDPOINT_NUM; ep_num++) {
      if (ep_num != ep_addr) {
        if (max_packet_size < ptr_ro_info->ptr_rw_info->ep_info[ep_num][EP_OUT_INDEX].max_packet_size) {
          max_packet_size = ptr_ro_info->ptr_rw_info->ep_info[ep_num][EP_OUT_INDEX].max_packet_size;
        }
      } else {
        if (max_packet_size < ep_max_packet_size) {
          max_packet_size = ep_max_packet_size;
        }
      }
    }

    // Calculate Rx FIFO size:
    // = (5 * number of control endpoints + 8) + ((largest USB packet used / 4) + 1 for status information) +
    //   (2 * number of OUT endpoints) + 1 for Global NAK
    // = (5 * 1 * 8) + ((max_packet_size + 3) /4) + 1 + (2 * USBD_MAX_ENDPOINT_NUM) + 1
    // = 15 + ((max_packet_size + 3) /4) + (2 * USBD_MAX_ENDPOINT_NUM)
    rx_fifo_size = 15U + ((ep_max_packet_size + 3U) / 4U) + (2U * USBD_MAX_ENDPOINT_NUM);
    if (HAL_PCDEx_SetRxFiFo(ptr_ro_info->ptr_hpcd, rx_fifo_size) != HAL_OK) {
      return ARM_DRIVER_ERROR_PARAMETER;
    }
  }
#else                                   // If using HAL_PCDEx_PMAConfig function
  uint32_t pcd_addr;
  uint16_t max_packet_size;
  uint8_t  ep_num, req_ep;

  (void)ep_type;

  // Reconfigure endpoint buffers
  req_ep   = 0U;
  pcd_addr = ptr_ro_info->ptr_hpcd->Init.dev_endpoints * 8U;    // Offset start address for BTABLE
  for (ep_num = 0U; ep_num < ptr_ro_info->ptr_hpcd->Init.dev_endpoints; ep_num++) {
    // OUT Endpoint
    if (HAL_PCDEx_PMAConfig(ptr_ro_info->ptr_hpcd, ep_num, PCD_SNG_BUF, pcd_addr) != HAL_OK) {
      return ARM_DRIVER_ERROR_PARAMETER;
    }
    if (ep_addr == ep_num) {            // If this is the requested endpoint (max_packet_size is not in the structure yet)
      req_ep = 1U;
      max_packet_size = ep_max_packet_size;
    } else {                            // If this is not the requested endpoint (max_packet_size is in the structure)
      max_packet_size = ptr_ro_info->ptr_rw_info->ep_info[ep_num][EP_OUT_INDEX].max_packet_size;
    }
    pcd_addr += ((max_packet_size + 3U) / 4U) * 4U;

    // To refresh the PMA configuration the endpoint has to be opened
    if ((ep_addr != ep_num) && (req_ep != 0U) && (ptr_ro_info->ptr_rw_info->ep_info[ep_num][EP_OUT_INDEX].configured != 0U)) {
      (void)HAL_PCD_EP_Open(ptr_ro_info->ptr_hpcd, ep_num, ep_max_packet_size, ep_type);
    }

    // IN Endpoint
    if (HAL_PCDEx_PMAConfig(ptr_ro_info->ptr_hpcd, 0x80U | ep_num, PCD_SNG_BUF, pcd_addr) != HAL_OK) {
      return ARM_DRIVER_ERROR_PARAMETER;
    }
    if (ep_addr == (0x80U | ep_num)) {  // If this is the requested endpoint (max_packet_size is not in the structure yet)
      req_ep = 1U;
      max_packet_size = ep_max_packet_size;
    } else {                            // If this is not the requested endpoint (max_packet_size is in the structure)
      max_packet_size = ptr_ro_info->ptr_rw_info->ep_info[ep_num][EP_IN_INDEX].max_packet_size;
    }
    pcd_addr += ((max_packet_size + 3U) / 4U) * 4U;

    // To refresh the PMA configuration the endpoint has to be opened
    if ((ep_addr != (0x80U | ep_num)) && (req_ep != 0U) && (ptr_ro_info->ptr_rw_info->ep_info[ep_num][EP_IN_INDEX].configured != 0U)) {
      (void)HAL_PCD_EP_Open(ptr_ro_info->ptr_hpcd, ep_num | 0x80U, ep_max_packet_size, ep_type);
    }
  }
#endif

  return ARM_DRIVER_OK;
}

// Driver functions ************************************************************

/**
  \fn          ARM_DRIVER_VERSION USBD_GetVersion (void)
  \brief       Get driver version.
  \return      \ref ARM_DRIVER_VERSION
*/
static ARM_DRIVER_VERSION USBD_GetVersion (void) {
  return driver_version;
}

/**
  \fn          ARM_USBD_CAPABILITIES USBDn_GetCapabilities (const RO_Info_t * const ptr_ro_info)
  \brief       Get driver capabilities.
  \param[in]   ptr_ro_info     Pointer to USBD RO info structure (RO_Info_t)
  \return      \ref ARM_USBD_CAPABILITIES
*/
static ARM_USBD_CAPABILITIES USBDn_GetCapabilities (const RO_Info_t * const ptr_ro_info) {
  ARM_USBD_CAPABILITIES driver_capabilities;
  uint8_t               vbus_detection;

  // Clear capabilities structure
  memset(&driver_capabilities, 0, sizeof(ARM_USBD_CAPABILITIES));

  vbus_detection = 0U;
  if (ptr_ro_info->ptr_hpcd->Init.phy_itface == PCD_PHY_EMBEDDED) {
    // If embedded PHY (FS) is configured
#ifdef USBD_VARIANT_VBUS_SENSING
    if (ptr_ro_info->ptr_hpcd->Init.vbus_sensing_enable == ENABLE) {
      vbus_detection = 1U;
    }
#endif
  }
#ifdef USB_OTG_ULPI_PHY
  else if (ptr_ro_info->ptr_hpcd->Init.phy_itface == USB_OTG_ULPI_PHY) {
    // If external ULPI PHY (HS) is configured
    vbus_detection = 1U;
  }
#endif

  if (vbus_detection != 0U) {
    // If VBUS detection is available
    driver_capabilities.vbus_detection = 1U;
    driver_capabilities.event_vbus_on  = 1U;
    driver_capabilities.event_vbus_off = 1U;
  }

  return driver_capabilities;
}

/**
  \fn          int32_t USBDn_Initialize (const RO_Info_t * const        ptr_ro_info,
                                         ARM_USBD_SignalDeviceEvent_t   cb_device_event,
                                         ARM_USBD_SignalEndpointEvent_t cb_endpoint_event)
  \brief       Initialize USB Device Interface.
  \param[in]   ptr_ro_info        Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   cb_device_event    Pointer to \ref ARM_USBD_SignalDeviceEvent
  \param[in]   cb_endpoint_event  Pointer to \ref ARM_USBD_SignalEndpointEvent
  \return      \ref execution_status
*/
static int32_t USBDn_Initialize (const RO_Info_t * const        ptr_ro_info,
                                 ARM_USBD_SignalDeviceEvent_t   cb_device_event,
                                 ARM_USBD_SignalEndpointEvent_t cb_endpoint_event) {

  // Clear run-time info
  memset((void *)ptr_ro_info->ptr_rw_info, 0, sizeof(RW_Info_t));

  // Register callback functions
  ptr_ro_info->ptr_rw_info->cb_device_event   = cb_device_event;
  ptr_ro_info->ptr_rw_info->cb_endpoint_event = cb_endpoint_event;

  // Set driver status to initialized
  ptr_ro_info->ptr_rw_info->drv_status.initialized = 1U;

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBDn_Uninitialize (const RO_Info_t * const ptr_ro_info)
  \brief       De-initialize USB Device Interface.
  \param[in]   ptr_ro_info     Pointer to USBD RO info structure (RO_Info_t)
  \return      \ref execution_status
*/
static int32_t USBDn_Uninitialize (const RO_Info_t * const ptr_ro_info) {

  if (ptr_ro_info->ptr_rw_info->drv_status.powered != 0U) {
    // If peripheral is powered, power off the peripheral
    (void)USBDn_PowerControl(ptr_ro_info, ARM_POWER_OFF);
  }

  // Clear run-time info
  memset((void *)ptr_ro_info->ptr_rw_info, 0, sizeof(RW_Info_t));

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBDn_PowerControl (const RO_Info_t * const ptr_ro_info, ARM_POWER_STATE state)
  \brief       Control USB Device Interface Power.
  \param[in]   ptr_ro_info     Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   state  Power state
  \return      \ref execution_status
*/
static int32_t USBDn_PowerControl (const RO_Info_t * const ptr_ro_info, ARM_POWER_STATE state) {
  ARM_USBD_SignalDeviceEvent_t   cb_device_event;
  ARM_USBD_SignalEndpointEvent_t cb_endpoint_event;
  DriverStatus_t                 drv_status;
  uint8_t                        ep_num;
  uint8_t                        ep_dir;

  switch (state) {
    case ARM_POWER_FULL:
      if (ptr_ro_info->ptr_rw_info->drv_status.initialized == 0U) {
        return ARM_DRIVER_ERROR;
      }

      // Store variables we need to preserve
      cb_device_event   = ptr_ro_info->ptr_rw_info->cb_device_event;
      cb_endpoint_event = ptr_ro_info->ptr_rw_info->cb_endpoint_event;
      drv_status        = ptr_ro_info->ptr_rw_info->drv_status;

      // Clear run-time info
      memset((void *)ptr_ro_info->ptr_rw_info, 0, sizeof(RW_Info_t));

      // Restore variables we wanted to preserve
      ptr_ro_info->ptr_rw_info->cb_device_event   = cb_device_event;
      ptr_ro_info->ptr_rw_info->cb_endpoint_event = cb_endpoint_event;
      ptr_ro_info->ptr_rw_info->drv_status        = drv_status;

      // Initialize pins, clocks, interrupts and peripheral
      if (HAL_PCD_Init(ptr_ro_info->ptr_hpcd) != HAL_OK) {
        return ARM_DRIVER_ERROR;
      }

      // Set driver status to powered
      ptr_ro_info->ptr_rw_info->drv_status.powered = 1U;
      break;

    case ARM_POWER_OFF:

      // Abort all endpoint transfers
      for (ep_num = 0U; ep_num < ptr_ro_info->ptr_hpcd->Init.dev_endpoints; ep_num++) {
        for (ep_dir = 0U; ep_dir < 2U; ep_dir++) {
          (void)USBDn_EndpointTransferAbort(ptr_ro_info, (uint8_t)(ep_dir << 7) | ep_num);
        }
      }

      // Stop USB Device operation
      (void)HAL_PCD_Stop(ptr_ro_info->ptr_hpcd);

      // De-initialize pins, clocks, interrupts and peripheral
      (void)HAL_PCD_DeInit(ptr_ro_info->ptr_hpcd);

      // Set driver status to not powered
      ptr_ro_info->ptr_rw_info->drv_status.powered = 0U;

      // Store variables we need to preserve
      cb_device_event   = ptr_ro_info->ptr_rw_info->cb_device_event;
      cb_endpoint_event = ptr_ro_info->ptr_rw_info->cb_endpoint_event;
      drv_status        = ptr_ro_info->ptr_rw_info->drv_status;

      // Clear run-time info
      memset((void *)ptr_ro_info->ptr_rw_info, 0, sizeof(RW_Info_t));

      // Restore variables we wanted to preserve
      ptr_ro_info->ptr_rw_info->cb_device_event   = cb_device_event;
      ptr_ro_info->ptr_rw_info->cb_endpoint_event = cb_endpoint_event;
      ptr_ro_info->ptr_rw_info->drv_status        = drv_status;
      break;

    case ARM_POWER_LOW:
      return ARM_DRIVER_ERROR_UNSUPPORTED;

    default:
      return ARM_DRIVER_ERROR_PARAMETER;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBDn_DeviceConnect (const RO_Info_t * const ptr_ro_info)
  \brief       Connect USB Device.
  \param[in]   ptr_ro_info     Pointer to USBD RO info structure (RO_Info_t)
  \return      \ref execution_status
*/
static int32_t USBDn_DeviceConnect (const RO_Info_t * const ptr_ro_info) {

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  if (HAL_PCD_Start(ptr_ro_info->ptr_hpcd) != HAL_OK) {
    return ARM_DRIVER_ERROR;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBDn_DeviceDisconnect (const RO_Info_t * const ptr_ro_info)
  \brief       Disconnect USB Device.
  \param[in]   ptr_ro_info     Pointer to USBD RO info structure (RO_Info_t)
  \return      \ref execution_status
*/
static int32_t USBDn_DeviceDisconnect (const RO_Info_t * const ptr_ro_info) {

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  if (HAL_PCD_DevDisconnect(ptr_ro_info->ptr_hpcd) != HAL_OK) {
    return ARM_DRIVER_ERROR;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          ARM_USBD_STATE USBDn_DeviceGetState (const RO_Info_t * const ptr_ro_info)
  \brief       Get current USB Device State.
  \param[in]   ptr_ro_info     Pointer to USBD RO info structure (RO_Info_t)
  \return      Device State \ref ARM_USBD_STATE
*/
static ARM_USBD_STATE USBDn_DeviceGetState (const RO_Info_t * const ptr_ro_info) {
  ARM_USBD_STATE state;

  // Clear state structure
  memset(&state, 0, sizeof(ARM_USBD_STATE));

  // Process additionally handled communication information
  if (ptr_ro_info->ptr_rw_info->usbd_state.vbus != 0U) {
    state.vbus = 1U;
  }
  switch (ptr_ro_info->ptr_rw_info->usbd_state.speed) {
    case ARM_USB_SPEED_FULL:
      state.speed = ARM_USB_SPEED_FULL;
      break;
    case ARM_USB_SPEED_HIGH:
      state.speed = ARM_USB_SPEED_HIGH;
      break;
    default:
      break;
  }
  if (ptr_ro_info->ptr_rw_info->usbd_state.active != 0U) {
    state.active = 1U;
  }

  return state;
}

/**
  \fn          int32_t USBDn_DeviceRemoteWakeup (const RO_Info_t * const ptr_ro_info)
  \brief       Trigger USB Remote Wakeup.
  \param[in]   ptr_ro_info     Pointer to USBD RO info structure (RO_Info_t)
  \return      \ref execution_status
*/
static int32_t USBDn_DeviceRemoteWakeup (const RO_Info_t * const ptr_ro_info) {

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  if (HAL_PCD_ActivateRemoteWakeup(ptr_ro_info->ptr_hpcd) != HAL_OK) {
    return ARM_DRIVER_ERROR;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBDn_DeviceSetAddress (const RO_Info_t * const ptr_ro_info, uint8_t dev_addr)
  \brief       Set USB Device Address.
  \param[in]   ptr_ro_info     Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   dev_addr  Device Address
  \return      \ref execution_status
*/
static int32_t USBDn_DeviceSetAddress (const RO_Info_t * const ptr_ro_info, uint8_t dev_addr) {

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  if (HAL_PCD_SetAddress(ptr_ro_info->ptr_hpcd, dev_addr) != HAL_OK) {
    return ARM_DRIVER_ERROR;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBDn_ReadSetupPacket (const RO_Info_t * const ptr_ro_info, uint8_t *setup)
  \brief       Read setup packet received over Control Endpoint.
  \param[in]   ptr_ro_info     Pointer to USBD RO info structure (RO_Info_t)
  \param[out]  setup  Pointer to buffer for setup packet
  \return      \ref execution_status
*/
static int32_t USBDn_ReadSetupPacket (const RO_Info_t * const ptr_ro_info, uint8_t *setup) {

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  if (ptr_ro_info->ptr_rw_info->setup_received == 0U) {
    return ARM_DRIVER_ERROR;
  }

  do {
    ptr_ro_info->ptr_rw_info->setup_received = 0U;
    memcpy(setup, (const uint32_t *)(uint32_t)ptr_ro_info->ptr_rw_info->setup_packet, 8);
  } while (ptr_ro_info->ptr_rw_info->setup_received != 0U);

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBDn_EndpointConfigure (const RO_Info_t * const ptr_ro_info,
                                                      uint8_t           ep_addr,
                                                      uint8_t           ep_type,
                                                      uint16_t          ep_max_packet_size)
  \brief       Configure USB Endpoint.
  \param[in]   ptr_ro_info     Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   ep_addr  Endpoint Address
                - ep_addr.0..3: Address
                - ep_addr.7:    Direction
  \param[in]   ep_type  Endpoint Type (ARM_USB_ENDPOINT_xxx)
  \param[in]   ep_max_packet_size Endpoint Maximum Packet Size
  \return      \ref execution_status
*/
static int32_t USBDn_EndpointConfigure (const RO_Info_t * const ptr_ro_info,
                                              uint8_t           ep_addr,
                                              uint8_t           ep_type,
                                              uint16_t          ep_max_packet_size) {
  EP_Info_t *ptr_ep;
  int32_t    ret;
  uint8_t    ep_num;
  uint8_t    ep_dir;

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  ep_num = EP_NUM(ep_addr);

  if (ep_num >= ptr_ro_info->ptr_hpcd->Init.dev_endpoints) {
    return ARM_DRIVER_ERROR_PARAMETER;
  }

  ep_dir = EP_DIR(ep_addr);
  ptr_ep = &ptr_ro_info->ptr_rw_info->ep_info[ep_num][ep_dir];

  // Close endpoint
  if (HAL_PCD_EP_Close(ptr_ro_info->ptr_hpcd, ep_addr) != HAL_OK) {
    return ARM_DRIVER_ERROR;
  }

  // Clear Endpoint information
  memset((void *)ptr_ep, 0, sizeof(EP_Info_t));
  if (ep_num == 0U) {
    ptr_ro_info->ptr_rw_info->ep0_data[ep_dir] = NULL;
    ptr_ro_info->ptr_rw_info->ep0_num [ep_dir] = 0U;
  }

  // Reconfigure Endpoint buffer
  ret = USBDn_EndpointConfigureBuffer(ptr_ro_info, ep_addr, ep_type, ep_max_packet_size);

  if (ret != ARM_DRIVER_OK) {           // If buffer configuration failed
    return ret;
  }

  // Store max packet size information (for Endpoint buffer configuration)
  ptr_ep->max_packet_size = ep_max_packet_size;

  // Open endpoint
  if (HAL_PCD_EP_Open(ptr_ro_info->ptr_hpcd, ep_addr, ep_max_packet_size, ep_type) != HAL_OK) {
    return ARM_DRIVER_ERROR;
  }

  // Update endpoint configured status
  ptr_ep->configured = 1U;

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBDn_EndpointUnconfigure (const RO_Info_t * const ptr_ro_info, uint8_t ep_addr)
  \brief       Unconfigure USB Endpoint.
  \param[in]   ptr_ro_info     Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   ep_addr  Endpoint Address
                - ep_addr.0..3: Address
                - ep_addr.7:    Direction
  \return      \ref execution_status
*/
static int32_t USBDn_EndpointUnconfigure (const RO_Info_t * const ptr_ro_info, uint8_t ep_addr) {
  EP_Info_t *ptr_ep;
  uint8_t    ep_num;
  uint8_t    ep_dir;

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  ep_num = EP_NUM(ep_addr);

  if (ep_num >= ptr_ro_info->ptr_hpcd->Init.dev_endpoints) {
    return ARM_DRIVER_ERROR_PARAMETER;
  }

  ep_dir = EP_DIR(ep_addr);
  ptr_ep = &ptr_ro_info->ptr_rw_info->ep_info[ep_num][ep_dir];

  // Close endpoint
  if (HAL_PCD_EP_Close(ptr_ro_info->ptr_hpcd, ep_addr) != HAL_OK) {
    return ARM_DRIVER_ERROR;
  }

  // Clear Endpoint information
  memset((void *)ptr_ep, 0, sizeof(EP_Info_t));
  if (ep_num == 0U) {
    ptr_ro_info->ptr_rw_info->ep0_data[ep_dir] = NULL;
    ptr_ro_info->ptr_rw_info->ep0_num [ep_dir] = 0U;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBDn_EndpointStall (const RO_Info_t * const ptr_ro_info, uint8_t ep_addr, bool stall)
  \brief       Set/Clear Stall for USB Endpoint.
  \param[in]   ptr_ro_info     Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   ep_addr  Endpoint Address
                - ep_addr.0..3: Address
                - ep_addr.7:    Direction
  \param[in]   stall  Operation
                - \b false Clear
                - \b true Set
  \return      \ref execution_status
*/
static int32_t USBDn_EndpointStall (const RO_Info_t * const ptr_ro_info, uint8_t ep_addr, bool stall) {

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  if (stall != 0U) {                    // If request to set STALL
    if (HAL_PCD_EP_SetStall(ptr_ro_info->ptr_hpcd, ep_addr) != HAL_OK) {
      return ARM_DRIVER_ERROR;
    }
  } else {                              // If request to clear STALL
    if (HAL_PCD_EP_ClrStall(ptr_ro_info->ptr_hpcd, ep_addr) != HAL_OK) {
      return ARM_DRIVER_ERROR;
    }
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBDn_EndpointTransfer (const RO_Info_t * const ptr_ro_info, uint8_t ep_addr, uint8_t *data, uint32_t num)
  \brief       Read data from or Write data to USB Endpoint.
  \param[in]   ptr_ro_info     Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   ep_addr  Endpoint Address
                - ep_addr.0..3: Address
                - ep_addr.7:    Direction
  \param[out]  data Pointer to buffer for data to read or with data to write
  \param[in]   num  Number of data bytes to transfer
  \return      \ref execution_status
*/
static int32_t USBDn_EndpointTransfer (const RO_Info_t * const ptr_ro_info, uint8_t ep_addr, uint8_t *data, uint32_t num) {
  EP_Info_t *ptr_ep;
  uint8_t    ep_num;
  uint8_t    ep_dir;

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  ep_num = EP_NUM(ep_addr);

  if (ep_num >= ptr_ro_info->ptr_hpcd->Init.dev_endpoints) {
    return ARM_DRIVER_ERROR_PARAMETER;
  }

  ep_dir = EP_DIR(ep_addr);
  ptr_ep = &ptr_ro_info->ptr_rw_info->ep_info[ep_num][ep_dir];

  // Clear number of transferred bytes
  ptr_ep->num_transferred_total = 0U;

  if (ep_num == 0U) {
    // Register pointer to data and number of bytes to transfer for Endpoint 0
    ptr_ro_info->ptr_rw_info->ep0_data[ep_dir] = data;
    ptr_ro_info->ptr_rw_info->ep0_num [ep_dir] = num;
  }

  ptr_ep->num_transferring = num;
  if ((ep_num == 0U) && (ptr_ep->max_packet_size < num)) {
    // For Endpoint 0 total transfer is done in transfers of maximum packet size at a time
    ptr_ep->num_transferring = ptr_ep->max_packet_size;
  }

  if (ep_dir != 0U) {                   // If IN Endpoint
    if (HAL_PCD_EP_Transmit(ptr_ro_info->ptr_hpcd, ep_addr, (uint8_t *)data, ptr_ep->num_transferring) != HAL_OK) {
      return ARM_DRIVER_ERROR;
    }
  } else {                              // If OUT Endpoint
    if (HAL_PCD_EP_Receive(ptr_ro_info->ptr_hpcd, ep_addr, (uint8_t *)data, ptr_ep->num_transferring) != HAL_OK) {
      return ARM_DRIVER_ERROR;
    }
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          uint32_t USBDn_EndpointTransferGetResult (const RO_Info_t * const ptr_ro_info, uint8_t ep_addr)
  \brief       Get result of USB Endpoint transfer.
  \param[in]   ptr_ro_info     Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   ep_addr  Endpoint Address
                - ep_addr.0..3: Address
                - ep_addr.7:    Direction
  \return      number of successfully transferred data bytes
*/
static uint32_t USBDn_EndpointTransferGetResult (const RO_Info_t * const ptr_ro_info, uint8_t ep_addr) {
  uint8_t ep_num;

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return 0U;
  }

  ep_num = EP_NUM(ep_addr);

  if (ep_num >= ptr_ro_info->ptr_hpcd->Init.dev_endpoints) {
    return 0U;
  }

  return ptr_ro_info->ptr_rw_info->ep_info[ep_num][EP_DIR(ep_addr)].num_transferred_total;
}

/**
  \fn          int32_t USBDn_EndpointTransferAbort (const RO_Info_t * const ptr_ro_info, uint8_t ep_addr)
  \brief       Abort current USB Endpoint transfer.
  \param[in]   ptr_ro_info     Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   ep_addr  Endpoint Address
                - ep_addr.0..3: Address
                - ep_addr.7:    Direction
  \return      \ref execution_status
*/
static int32_t USBDn_EndpointTransferAbort (const RO_Info_t * const ptr_ro_info, uint8_t ep_addr) {
  uint8_t ep_num;

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  ep_num = EP_NUM(ep_addr);

  if (ep_num >= ptr_ro_info->ptr_hpcd->Init.dev_endpoints) {
    return ARM_DRIVER_ERROR_PARAMETER;
  }

  // HAL_PCD_EP_Abort returns HAL_ERROR if aborting interrupt OUT endpoint, so we ignore return value
  (void)HAL_PCD_EP_Abort(ptr_ro_info->ptr_hpcd, ep_addr);

  return ARM_DRIVER_OK;
}

/**
  \fn          uint16_t USBDn_GetFrameNumber (const RO_Info_t * const ptr_ro_info)
  \brief       Get current USB Frame Number.
  \param[in]   ptr_ro_info     Pointer to USBD RO info structure (RO_Info_t)
  \return      Frame Number
*/
static uint16_t USBDn_GetFrameNumber (const RO_Info_t * const ptr_ro_info) {
  (void)ptr_ro_info;

  return 0U;
}

// HAL callback functions ******************************************************

/**
  \fn          void HAL_PCD_DataOutStageCallback (PCD_HandleTypeDef *hpcd, uint8_t epnum)
  \brief       Data OUT stage callback.
  \param[in]   hpcd     PCD handle
  \param[in]   epnum    endpoint number
  */
void HAL_PCD_DataOutStageCallback (PCD_HandleTypeDef *hpcd, uint8_t epnum) {
  const RO_Info_t *ptr_ro_info;
        EP_Info_t *ptr_ep;
        uint32_t   num_transferred;
        uint32_t   num_to_transfer;
        uint8_t   *data_to_transfer;
        uint32_t   event;

  ptr_ro_info = USBD_GetInfo(hpcd);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  ptr_ep = &ptr_ro_info->ptr_rw_info->ep_info[epnum][EP_OUT_INDEX];

  event  = 0U;

  if (epnum != 0U) {                    // Endpoint other than 0
    ptr_ep->num_transferred_total = HAL_PCD_EP_GetRxCount(hpcd, epnum);
    event = ARM_USBD_EVENT_OUT;
  } else {                              // Endpoint 0
    num_transferred = HAL_PCD_EP_GetRxCount(hpcd, epnum);
    ptr_ep->num_transferred_total += num_transferred;
    if ((num_transferred < ptr_ep->max_packet_size) || (ptr_ep->num_transferred_total == ptr_ro_info->ptr_rw_info->ep0_num[EP_OUT_INDEX])) {
      // If all data was transferred
      event = ARM_USBD_EVENT_OUT;
    } else {
      // If there is more data to transfer
      data_to_transfer = ptr_ro_info->ptr_rw_info->ep0_data[EP_OUT_INDEX] + ptr_ep->num_transferred_total;
      num_to_transfer  = ptr_ro_info->ptr_rw_info->ep0_num [EP_OUT_INDEX] - ptr_ep->num_transferred_total;
      if (num_to_transfer > ptr_ep->max_packet_size) {
        num_to_transfer = ptr_ep->max_packet_size;
      }
      ptr_ep->num_transferring = num_to_transfer;
      (void)HAL_PCD_EP_Receive(ptr_ro_info->ptr_hpcd, epnum, data_to_transfer, num_to_transfer);
    }
  }

  if ((ptr_ro_info->ptr_rw_info->cb_endpoint_event != NULL) && (event != 0U)) {
    ptr_ro_info->ptr_rw_info->cb_endpoint_event(epnum, event);
  }
}

/**
  \fn          void HAL_PCD_DataInStageCallback (PCD_HandleTypeDef *hpcd, uint8_t epnum)
  \brief       Data IN stage callback.
  \param[in]   hpcd     PCD handle
  \param[in]   epnum    endpoint number
  */
void HAL_PCD_DataInStageCallback (PCD_HandleTypeDef *hpcd, uint8_t epnum) {
  const RO_Info_t *ptr_ro_info;
        EP_Info_t *ptr_ep;
        uint32_t   num_to_transfer;
        uint8_t   *data_to_transfer;
        uint32_t   event;

  ptr_ro_info = USBD_GetInfo(hpcd);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  ptr_ep = &ptr_ro_info->ptr_rw_info->ep_info[epnum][EP_IN_INDEX];

  event  = 0U;

  ptr_ep->num_transferred_total += ptr_ep->num_transferring;
  if (epnum != 0U) {                    // Endpoint other than 0
    event = ARM_USBD_EVENT_IN;
  } else {                              // Endpoint 0
    if(ptr_ep->num_transferred_total == ptr_ro_info->ptr_rw_info->ep0_num[EP_IN_INDEX]) {
      // If all data was transferred
      event = ARM_USBD_EVENT_IN;
    } else {
      // If there is more data to transfer
      data_to_transfer = ptr_ro_info->ptr_rw_info->ep0_data[EP_IN_INDEX] + ptr_ep->num_transferred_total;
      num_to_transfer  = ptr_ro_info->ptr_rw_info->ep0_num [EP_IN_INDEX] - ptr_ep->num_transferred_total;
      if (num_to_transfer > ptr_ep->max_packet_size) {
        num_to_transfer = ptr_ep->max_packet_size;
      }
      ptr_ep->num_transferring = num_to_transfer;
      (void)HAL_PCD_EP_Transmit(ptr_ro_info->ptr_hpcd, epnum | 0x80, data_to_transfer, num_to_transfer);
    }
  }

  if ((ptr_ro_info->ptr_rw_info->cb_endpoint_event != NULL) && (event != 0U)) {
    ptr_ro_info->ptr_rw_info->cb_endpoint_event(epnum | ARM_USB_ENDPOINT_DIRECTION_MASK, event);
  }
}

/**
  \fn          HAL_PCD_SetupStageCallback (PCD_HandleTypeDef *hpcd)
  \brief       Setup stage callback.
  \param[in]   hpcd     PCD handle
  */
void HAL_PCD_SetupStageCallback (PCD_HandleTypeDef *hpcd) {
  const RO_Info_t *ptr_ro_info;

  ptr_ro_info = USBD_GetInfo(hpcd);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  memcpy((void *)(uint32_t)ptr_ro_info->ptr_rw_info->setup_packet, hpcd->Setup, 8);
  ptr_ro_info->ptr_rw_info->setup_received = 1U;

  // Analyze Setup packet for SetAddress
  if ((ptr_ro_info->ptr_rw_info->setup_packet[0] & 0xFFFFU) == 0x0500U) {
    (void)USBDn_DeviceSetAddress(ptr_ro_info, (ptr_ro_info->ptr_rw_info->setup_packet[0] >> 16) & 0xFFU);
  }

  if (ptr_ro_info->ptr_rw_info->cb_endpoint_event != NULL) {
    ptr_ro_info->ptr_rw_info->cb_endpoint_event(0U, ARM_USBD_EVENT_SETUP);
  }
}

/**
  \fn          void HAL_PCD_ResetCallback (PCD_HandleTypeDef *hpcd)
  \brief       USB Reset callback.
  \param[in]   hpcd     PCD handle
  */
void HAL_PCD_ResetCallback (PCD_HandleTypeDef *hpcd) {
  const RO_Info_t *ptr_ro_info;

  ptr_ro_info = USBD_GetInfo(hpcd);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  // Clear Endpoints information
  ptr_ro_info->ptr_rw_info->ep0_data[EP_OUT_INDEX] = NULL;
  ptr_ro_info->ptr_rw_info->ep0_num [EP_OUT_INDEX] = 0U;
  ptr_ro_info->ptr_rw_info->ep0_data[EP_IN_INDEX]  = NULL;
  ptr_ro_info->ptr_rw_info->ep0_num [EP_IN_INDEX]  = 0U;
  memset((void *)ptr_ro_info->ptr_rw_info->ep_info, 0, 2 * USBD_MAX_ENDPOINT_NUM * sizeof(EP_Info_t));

  // Reset USBD state information
  ptr_ro_info->ptr_rw_info->usbd_state.speed  = ARM_USB_SPEED_FULL;
  ptr_ro_info->ptr_rw_info->usbd_state.active = 0U;

  if (ptr_ro_info->ptr_rw_info->cb_device_event != NULL) {
    ptr_ro_info->ptr_rw_info->cb_device_event(ARM_USBD_EVENT_RESET);
  }

  // USB Speed
#ifdef PCD_SPEED_HIGH
  if (hpcd->Init.speed == PCD_SPEED_HIGH) {
    ptr_ro_info->ptr_rw_info->usbd_state.speed = ARM_USB_SPEED_HIGH;
    if (ptr_ro_info->ptr_rw_info->cb_device_event != NULL) {
      ptr_ro_info->ptr_rw_info->cb_device_event(ARM_USBD_EVENT_HIGH_SPEED);
    }
  }
#endif

  // Configure Endpoint 0 OUT
  (void)USBDn_EndpointConfigure(ptr_ro_info, 0x00U, ARM_USB_ENDPOINT_CONTROL, USBD_EP0_MAX_PACKET_SIZE);

  // Configure Endpoint 0 IN
  (void)USBDn_EndpointConfigure(ptr_ro_info, 0x80U, ARM_USB_ENDPOINT_CONTROL, USBD_EP0_MAX_PACKET_SIZE);

  // After reset we consider USB as active
  ptr_ro_info->ptr_rw_info->usbd_state.active = 1U;
}

/**
  \fn          void HAL_PCD_SuspendCallback (PCD_HandleTypeDef *hpcd)
  \brief       Suspend event callback.
  \param[in]   hpcd     PCD handle
  */
void HAL_PCD_SuspendCallback (PCD_HandleTypeDef *hpcd) {
  const RO_Info_t *ptr_ro_info;

  ptr_ro_info = USBD_GetInfo(hpcd);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  ptr_ro_info->ptr_rw_info->usbd_state.active = 0U;

  if (ptr_ro_info->ptr_rw_info->cb_device_event != NULL) {
    ptr_ro_info->ptr_rw_info->cb_device_event(ARM_USBD_EVENT_SUSPEND);
  }
}

/**
  \fn          void HAL_PCD_ResumeCallback (PCD_HandleTypeDef *hpcd)
  \brief       Resume event callback.
  \param[in]   hpcd     PCD handle
  */
void HAL_PCD_ResumeCallback (PCD_HandleTypeDef *hpcd) {
  const RO_Info_t *ptr_ro_info;

  ptr_ro_info = USBD_GetInfo(hpcd);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  ptr_ro_info->ptr_rw_info->usbd_state.active = 1U;

  if (ptr_ro_info->ptr_rw_info->cb_device_event != NULL) {
    ptr_ro_info->ptr_rw_info->cb_device_event(ARM_USBD_EVENT_RESUME);
  }
}

/**
  \fn          void HAL_PCD_ISOOUTIncompleteCallback (PCD_HandleTypeDef *hpcd, uint8_t epnum)
  \brief       Incomplete ISO OUT callback.
  \param[in]   hpcd     PCD handle
  \param[in]   epnum    endpoint number
  */
void HAL_PCD_ISOOUTIncompleteCallback (PCD_HandleTypeDef *hpcd, uint8_t epnum) {
  (void)hpcd;
  (void)epnum;

  // NOTE : This function should not be modified, when the callback is needed,
  //        the HAL_PCD_ISOOUTIncompleteCallback could be implemented in the user file
}

/**
  \fn          void HAL_PCD_ISOINIncompleteCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
  \brief       Incomplete ISO IN callback.
  \param[in]   hpcd     PCD handle
  \param[in]   epnum    endpoint number
  */
void HAL_PCD_ISOINIncompleteCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum) {
  (void)hpcd;
  (void)epnum;

  // NOTE : This function should not be modified, when the callback is needed,
  //        the HAL_PCD_ISOINIncompleteCallback could be implemented in the user file
}

/**
  \fn          void HAL_PCD_ConnectCallback (PCD_HandleTypeDef *hpcd)
  \brief       Connection event callback.
  \param[in]   hpcd     PCD handle
  */
void HAL_PCD_ConnectCallback (PCD_HandleTypeDef *hpcd) {
  const RO_Info_t *ptr_ro_info;

  ptr_ro_info = USBD_GetInfo(hpcd);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  ptr_ro_info->ptr_rw_info->usbd_state.vbus = 1U;

  if (ptr_ro_info->ptr_rw_info->cb_device_event != NULL) {
    ptr_ro_info->ptr_rw_info->cb_device_event(ARM_USBD_EVENT_VBUS_ON);
  }
}

/**
  \fn          void HAL_PCD_DisconnectCallback (PCD_HandleTypeDef *hpcd)
  \brief       Disconnection event callback.
  \param[in]   hpcd     PCD handle
  */
void HAL_PCD_DisconnectCallback (PCD_HandleTypeDef *hpcd) {
  const RO_Info_t *ptr_ro_info;

  ptr_ro_info = USBD_GetInfo(hpcd);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  ptr_ro_info->ptr_rw_info->usbd_state.vbus = 0U;

  if (ptr_ro_info->ptr_rw_info->cb_device_event != NULL) {
    ptr_ro_info->ptr_rw_info->cb_device_event(ARM_USBD_EVENT_VBUS_OFF);
  }
}

// Local driver functions definitions (for instances)
#ifdef MX_USBD0
FUNCS_DEFINE(0)
#endif
#ifdef MX_USBD1
FUNCS_DEFINE(1)
#endif

// Global driver structures ****************************************************

#ifdef MX_USBD0
USBD_DRIVER(0)
#endif

#ifdef MX_USBD1
USBD_DRIVER(1)
#endif

#endif  // DRIVER_CONFIG_VALID

/*! \endcond */
